using System;
using System.Collections.Generic;

namespace VirtualRink.Library.Simulation
{
    public class SimpleGameSimulator : ISimulator
    {
        Game game;
        TimeSpan duration;
		Random random;

		public SimpleGameSimulator()
		{
			this.random = new Random();
		}

        private Team HomeTeam
        {
            get { return game.HomeTeam; }
        }

        private Team VisitingTeam
        {
            get { return game.VisitingTeam; }
        }

        public void Simulate(Game game)
        {
            this.game = game;

            Validate();
            DateTime start = DateTime.Now;

            this.SimulateScoring();
            this.SimulatePenalties();

			foreach (Goal goal in game.Goals)
				if (goal.Shooter.Team == game.HomeTeam)
					game.HomeGoals++;
				else
					game.VisitorGoals++;

            DateTime end = DateTime.Now;
            duration = end.Subtract(start);
        }

        void Validate()
        {
            if(this.game.Played)
            {
                throw new SimulationException("This game has already been played.");
            }

            if(Roster.IsValid(this.HomeTeam.Players) == false)
            {
                throw new SimulationException("Home team roster is not valid.");
            }
            if(Roster.IsValid(this.VisitingTeam.Players) == false)
            {
                throw new SimulationException("Visiting team roster is not valid.");
            }
        }

        void SimulatePenalties()
        {
            int penalties = 0;


            //get total number of penalties
            penalties = Randomizer.NextNormal(6, 3);

            if (penalties < 1)
            {
                return;
            }

            //initialize array of game seconds
            int[] penaltyGameSeconds = new int[penalties];


            for (int i = 0; i < penaltyGameSeconds.Length; i++)
            {
                penaltyGameSeconds[i] = Randomizer.Next(3, 3600);
                this.SimulatePenalty(penaltyGameSeconds[i]);
            }

        }

        void SimulateScoring()
        {
            //get total number of shots
            int shots = Randomizer.NextNormal(50, 10);

            //initialize array of game seconds
            int[] shotGameSeconds = new int[shots];

            for(int i = 0;i < shotGameSeconds.Length;i++)
            {
                shotGameSeconds[i] = Randomizer.Next(3, 3600);
                this.SimulateShot(shotGameSeconds[i]);
            }

            //determine if the game is a tie
            bool tie = this.GameIsTie();

            //settle the tie
            if(tie)
            {
                this.SettleTie();

            }
        }

        public TimeSpan Duration
        {
            get { return duration; }
        }

        void SettleTie()
        {
            bool isTie = true;
            int nextSecondStart = 3600 + 3;
            int nextSecondMax = 20;
            int nextSecond;
            int max = int.MaxValue - 1;
            int count = 0;

            //play to the death.  no overtime limit.
            while(isTie)
            {
                nextSecond = Randomizer.Next(nextSecondStart, nextSecondStart + nextSecondMax);
                this.SimulateShot(nextSecond);
                nextSecondStart = nextSecond + 10;
                isTie = this.GameIsTie();

                count = count + 1;
                if (count >= max){
                    throw new SimulationException("Tie-breaking period never ended.");
                }
            }
        }

        private bool GameIsTie()
        {
            int homeCount = 0;
            int visitorCount = 0;
            foreach(Goal goal in game.Goals)
            {
                if(goal.Shooter.Team == game.HomeTeam)
                {
                    homeCount = homeCount + 1;
                }
                else
                {
                    visitorCount = visitorCount + 1;
                }
            }

            if(homeCount.Equals(visitorCount))
            {
                return true;
            }
            return false;

        }

        private void SimulatePenalty(int gameSecond)
        {
            //determine who is the offender
            Player offender = this.DetermineOffender();

            string penaltyType = this.DeterminePenaltyType();
            int secondsInPeriod = 60 * 20;

            Penalty penalty = new Penalty();
            penalty.Player = offender;
            penalty.PenaltyType = penaltyType;
            penalty.Game = this.game;
            penalty.Season = this.game.Season;
            penalty.League = this.game.League;
            penalty.Period = this.DeterminePeriod(gameSecond);
            int diff = gameSecond - (secondsInPeriod * (penalty.Period - 1));
            penalty.PeriodTime = diff.ToString();
            penalty.Duration = 2;

            this.game.Penalties.Add(penalty);

            if (penaltyType == Penalties.PenaltyFighting)
            {
                Player offender2 = this.DetermineOffender(offender);
                Penalty penalty2 = new Penalty();
                penalty2.Player = offender2;
                penalty2.PenaltyType = penaltyType;
                penalty2.Game = this.game;
                penalty2.Season = this.game.Season;
                penalty2.League = this.game.League;
                penalty2.Period = penalty.Period;
                penalty2.PeriodTime = penalty.PeriodTime;
                penalty2.Duration = penalty.Duration;
                this.game.Penalties.Add(penalty2);
            }
        }

        private string DeterminePenaltyType()
        {
            int random = Randomizer.Next(1, 100);
            if(random < 25)
            {
                return Penalties.PenaltyHolding;
            }
            if(random < 45)
            {
                return Penalties.PenaltyHooking;
            }
            if(random < 67)
            {
                return Penalties.PenaltySlashing;
            }
            if(random < 90)
            {
                return Penalties.PenaltyTripping;
            }
            if(random <= 100)
            {
                return Penalties.PenaltyFighting;
            }
            return Penalties.PenaltyHolding;
        }

        private void SimulateShot(int gameSecond)
        {
            //determine who is the shooter
            Player shooter = DetermineShooter();
            this.AddShot(shooter);

            //determine if it's a goal
            Player goalie;
            if(shooter.Team == game.HomeTeam)
            {
				goalie = game.VisitingTeam.GetPlayerByPosition("G");
            }
            else
            {
				goalie = game.HomeTeam.GetPlayerByPosition("G");
            }

            bool shotIsGoal = this.DetermineShotIsGoal(shooter, goalie);

            if(shotIsGoal)
            {
				Goal goal = new Goal();
                goal.Shooter = shooter;
                goal.Season = this.game.Season;
                goal.Game = this.game;
                goal.League = this.game.League;
                IList<Player> assists = GetAssists(goal.Shooter);
                if(assists.Count >= 1)
                {
                    goal.Assistor1 = assists[0];
                }
                if(assists.Count >= 2)
                {
                    goal.Assistor2 = assists[1];
                }

                goal.Period = this.DeterminePeriod(gameSecond);
                int secondsInPeriod = 60 * 20;
                int diff = gameSecond - (secondsInPeriod * (goal.Period - 1));
                goal.PeriodTime = diff.ToString();
                game.Goals.Add(goal);
            }
        }

        private IList<Player> GetAssists(Player shooter)
        {
            List<Player> players = new List<Player>();
            int random = Randomizer.Next(1, 100);
            if(random > 30)
            {
                Team team = shooter.Team;
                Player player = CreateAssistor(team, shooter);
                if(player != null 
                    && string.Compare(player.Position, Constants.GoalieAbbreviation, true) != 0)
                {
                    players.Add(player);
                }
            }

            if (players.Count == 1){
                random = Randomizer.Next(1, 100);
                if(random > 50)
                {
                    Team team = shooter.Team;
                    Player player = CreateAssistor(team, shooter);
                    if(player != null 
                        && string.Compare(player.Position, Constants.GoalieAbbreviation, true) != 0 
                        && player.Id != players[0].Id)
                    {
                        players.Add(player);
                    }
                }
            }

            return players;
        }

        private Player CreateAssistor(Team team, Player shooter)
        {
            int index = Randomizer.Next(0, team.Players.Count - 1);
            Player player = team.Players[index];
            if(player.Id != shooter.Id)
            {
                return player;
            }
            return null;
        }

        private int DeterminePeriod(int gameSecond)
        {
            int secondsInPeriod = 60 * 20;
            if(gameSecond <= secondsInPeriod)
            {
                return 1;
            }
            else if(gameSecond <= secondsInPeriod * 2)
            {
                return 2;
            }
            else if(gameSecond <= secondsInPeriod * 3)
            {
                return 3;
            }
            else
            {
                return 4;
            }
        }

        private Player DetermineShooter()
        {
            Team shootingTeam = this.DetermineShootingTeam();
            int index = Randomizer.Next(0, shootingTeam.Players.Count);
			Player player = shootingTeam.Players[index];
            while(player.Position == "G")
            {
				index = Randomizer.Next(0, shootingTeam.Players.Count);
				player = shootingTeam.Players[index];
            }
            return player;
        }

        private Player DetermineOffender()
        {
            List<Player> allPlayers = new List<Player>();
            allPlayers.AddRange(this.HomeTeam.Players);
            allPlayers.AddRange(this.VisitingTeam.Players);

            int index = Randomizer.Next(0, allPlayers.Count - 1);
            return allPlayers[index];

        }

        private Player DetermineOffender(Player partner)
        {
            List<Player> allPlayers = new List<Player>();
            allPlayers.AddRange(this.HomeTeam.Players);
            allPlayers.AddRange(this.VisitingTeam.Players);

            int index = Randomizer.Next(0, allPlayers.Count - 1);
            Player offender = allPlayers[index];
            while (offender == partner)
            {
                index = Randomizer.Next(0, allPlayers.Count - 1);
                offender = allPlayers[index];
            }
            return offender;

        }

        private Team DetermineShootingTeam()
        {
            int result = Randomizer.Next(1, 100);
            if(result > 50)
            {
                return this.HomeTeam;
            }
            else
            {
                return this.VisitingTeam;
            }

        }

        private bool DetermineShotIsGoal(Player shooter, Player goalie)
        {
            int result = Randomizer.Next(1, 100);
            if(result > 85)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        void AddShot(Player player)
        {
            foreach (ShotCount count in this.game.ShotCounts)
                if (count.Player == player)
                {
                    count.Shots++;
                    return;
                }

            ShotCount newCount = new ShotCount();
            newCount.Player = player;
            newCount.League = this.game.League;
            newCount.Season = this.game.Season;
            newCount.Shots = 1;
            newCount.Game = this.game;
            
        }

    }
}
